﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Fusion
{

    /// <summary> Describes a map of tiles </summary>
    public class TileMap : Entity
    {
        #region Fields


        public TileSheet sheet;
        int[] data;

        public int columns;
        public int rows;

		public Vector2 CelSize { get { return new Vector2( sheet.celWidth, sheet.celHeight ); } }
        public Vector2 DrawSize { get { return new Vector2( sheet.celWidth * columns, sheet.celHeight * rows ); } }
		public Vector2 ScaledSize { get { return DrawSize * Scale; } }

        #endregion

        #region Constructors

        /// <summary> Default Constructor, should only be used with care </summary>
        public TileMap()
        {
        }

        /// <summary> Copy Constructor </summary>
        /// <param name="copy">TileMap to copy from</param>
        public TileMap( TileMap copy )
        {
            Copy( copy );
        }

        /// <summary> XML Element Constructor </summary>
        /// <param name="element">XML Element to construct from</param>
        public TileMap( XElement element )
        {
            ReadFrom( element );
        }

        public TileMap( TileMap source, Rectangle area )
        {
            base.Copy( source ); // copy the source tilemap's properties
            Copy( source, area, false ); // then copy the data from the area rectangle
        }

        public TileMap( string sheetName, int Width, int Height, int Columns, int Rows )
        {
			sheet = Engine.Sheets.Get( sheetName );

            columns = Columns;
            rows = Rows;

            data = new int[rows * columns];
            for( int i = 0; i < columns * rows; i++ )
                data[i] = -1;
        }

        /// <summary> Copies the properties of the original image </summary>
        /// <param name="original">Image to copy from</param>
        /// <returns>this</returns>
        public override Entity Copy( Entity original )
        {
            base.Copy( original );

            if( original is TileMap )
            {
                TileMap tilemap = (TileMap)original;
				
				columns = tilemap.columns;
				rows = tilemap.rows;
                sheet = tilemap.sheet;

                data = new int[rows * columns];
                for( int i = 0; i < columns * rows; i++ )
                    data[i] = tilemap.data[i];
            }
            return this;
        }

        #endregion

        #region Methods

        /// <summary>  Draws the image with container's transform </summary>
        public override void Draw()
        {
			if( sheet.image.Texture == null )
				return;

            // Iterate through map tiles
            for( int y = 0; y < rows; y++ )
                for( int x=0; x < columns; x++ )
                {
                    int tile = data[x + (y * columns)];

                    if( tile >= 0 )
                    {
                        Rectangle frame = sheet.Frame( tile );
                        Vector2 celScale = Scale;

                        Vector2 celPos = new Vector2( x * sheet.celWidth, y * sheet.celHeight );
                        //celPos -= PixelSize / 2; // Center drawpos around origin
                        celPos *= celScale; // scale it down
                        celPos = Vector2.Transform( celPos, Matrix.CreateRotationZ( Angle ) ); // rotate it
                        celPos += Position; // put it where it's supposed to be

                        Engine.spriteBatch.Draw( sheet.image.Texture, celPos, frame, Color.White, Angle, Vector2.Zero, celScale, SpriteEffects.None, 0.5f );
                    }
                }
        }

        /// <summary> Retrieves draw frame for tile at specified index </summary>
        public Rectangle GetFrame( int x, int y )
        {
            if( x < 0 ) x = 0;
            if( x >= columns ) x = columns-1;
            if( y < 0 ) y = 0;
            if( y >= rows ) y = rows-1;

            return new Rectangle( x * sheet.celWidth, y * sheet.celHeight, sheet.celWidth, sheet.celHeight );
		}

		#endregion

		#region Editing

        /// <summary> Retrieves tile at specified index </summary>
        public int GetTile( int x, int y )
        {
			if( x < 0 || x >= columns || y < 0 || y >= rows )
				return -1;

            return data[x + ( y * columns )];
        }

        /// <summary> Sets tile at specified index </summary>
        /// <param name="data"> TileCell to place at index </param>
        public void SetTile( int x, int y, int cel )
        {
            if( x < 0 || x >= columns || y < 0 || y >= rows )
                return;

			int index = x + ( y * columns );

			data[index] = cel;
        }

		/// <summary>
		/// Resizes the tilemap based on edge values.
		/// </summary>
		/// <param name="left">Amount to add to the left edge</param>
		/// <param name="top">Amount to add to the left edge</param>
		/// <param name="bottom">Amount to add to the left edge</param>
		/// <param name="right">Amount to add to the left edge</param>
		public void Resize( int left, int top, int right, int bottom )
		{
			// copy existing tiles to brush
			TileMap copy = new TileMap( this, new Rectangle( 0, 0, columns, rows ) );

			// resize map
			columns += left + right;
			rows += top + bottom;
			if( columns < 1 )	columns = 1;
			if( rows < 1 ) rows = 1;

			data = new int[rows * columns];
			for( int i = 0; i < columns * rows; i++ )
				data[i] = -1;

			// paste old tiles
			copy.Paint( this, left, top );
		}


        /// <summary> Clears specified area </summary>
        public void Clear( Rectangle area )
        {
            for( int x=area.X; x < area.X + area.Width; x++ )
                for( int y=area.Y; y < area.Y + area.Height; y++ )
                    SetTile( x, y, -1 );
        }

        /// <summary> Blanks tilemap </summary>
        public void Blank()
        {
            for( int i = 0; i < columns * rows; i++ )
                data[i] = -1;
        }


        /// <summary> Copies the tiles from the source tilemap in a specified area </summary>
        /// <param name="source">The tilemap to copy from</param>
        /// <param name="area">The area in the source tilemap to copy from</param>
        /// <param name="cut">If true, tiles will be removed from target</param>
        public void Copy( TileMap source, Rectangle area, bool cut )
        {
            // set fields
            columns = area.Width; if( columns <= 0 ) columns = 1;
            rows = area.Height; if( rows <= 0 ) rows = 1;
            data = new int[columns * rows];

            // Copy data from source
            for( int y = 0; y < rows; y++ )
                for( int x = 0; x < columns; x++ )
                {
                    int localIndex = x + (y * columns);
                    int sx = area.X + x;
                    int sy = area.Y + y;
                    data[localIndex] = source.GetTile( sx, sy );
                    if( cut == true )
                        source.SetTile( sx, sy, -1 );
                }
        }

        /// <summary> Copies tiles from tile sheet </summary>
        /// <param name="area">The area to copy from</param>
        public void Copy( Rectangle area )
        {
            // Constrain selection rectangle
            if( area.X < 0 )
            {
                area.Width += area.X;
                area.X = 0;
            }
            if( area.Y < 0 )
            {
                area.Height += area.Y;
                area.Y = 0;
            }
            if( area.Width < 1 )
                area.Width = 1;
            if( area.Height < 1 )
                area.Height = 1;

            if( area.X + area.Width > sheet.columns )
                area.Width = sheet.columns - area.X;
            if( area.Y + area.Height > sheet.rows )
                area.Height = sheet.rows - area.Y;

            // set fields
            columns = area.Width;
            rows = area.Height;
            data = new int[area.Width * area.Height];

            // Populate brush data
            for( int y = 0; y < area.Height; y++ )
                for( int x = 0; x < area.Width; x++ )
                {
                    int localIndex = x + (y * area.Width);
                    int sx = area.X + x;
                    int sy = area.Y + y;
                    int index = (int)((area.X + x) + (area.Y + y) * columns);
					data[localIndex] = index;
                }
        }



        /// <summary> Paints this map onto the target tilemap in the specified frame </summary>
        /// <param name="target">The Tilemap to paint to</param>
        /// <param name="x">X Index to start painting at</param>
        /// <param name="y">Y Index to start painting at</param>
        public void Paint( TileMap target, int x, int y )
        {
            // Not going to paint anything, so gtfo to save time
            if( x > target.columns || y > target.rows
             || x + columns < 0 || y + rows < 0 )
                return;

            int left = x;
            int right = x + columns;

            int top = y;
            int bottom = y + rows;

            int i=0;
            for( int iy = top; iy < bottom; iy++ )
                for( int ix = left; ix < right; ix++ )
                {
                    target.SetTile( ix, iy, data[i] );
                    i++;
                }
        }

        /// <summary> Paints this map onto the target tilemap using specified masking technique </summary>
        /// <param name="target">The Tilemap to paint to</param>
        /// <param name="x">X Index to start painting at</param>
        /// <param name="y">Y Index to start painting at</param>
        /// <param name="mask">True paints only over blank tiles, False paints only over solid tiles</param>
        public void Paint( TileMap target, int x, int y, bool mask )
        {
            // don't be a retard, color INSIDE the lines.
            if( x > target.columns || y > target.rows
             || x + columns < 0 || y + rows < 0 )
                return;

            int left = x;
            int right = x + columns;

            int top = y;
            int bottom = y + rows;

            int i=0;
            for( int iy = top; iy < bottom; iy++ )
                for( int ix = left; ix < right; ix++ )
                {
                    if( (mask == true && target.GetTile( ix, iy ) < 0)
                     || (mask == false && target.GetTile( ix, iy ) >= 0) )
                        target.SetTile( ix, iy, data[i] );
                    i++;
                }
        }

        #endregion

		#region Intersect

		/// <summary> Intersects a bounding box with the tilemap </summary>
		/// <param name="boxPos">Top-Left corner of the testing box</param>
		/// <param name="boxSize">Width-Height of the testing box</param>
		/// <param name="projection">The depth the testing box is projected into the map </param>
		/// <returns>True if an intersection is detected</returns>
		public override bool Intersect( Vector2 boxPos, Vector2 boxSize, ref Vector2 projection )
		{
			if( !Hax.Intersect( boxPos, boxSize, Position, DrawSize, ref projection ) )
				return false;

			Vector2 tl = boxPos - Position;
			int w = sheet.celWidth;
			int h = sheet.celHeight;

			int left = (int)( tl.X / w );
			int top = (int)( tl.Y / h );
			int right = left + (int)( boxSize.X / w );
			int bottom = top + (int)( boxSize.Y / h );

			if( left < 0 ) left = 0;
			if( right >= columns ) right = columns - 1;
			if( top < 0 ) top = 0;
			if( bottom >= rows ) bottom = rows - 1;
			if( right < 0 || left > columns || bottom < 0 || top > rows )
				return false;
			
						
			bool intersect = false;
			Vector2 testProj = Vector2.Zero;
			projection = Vector2.Zero;

			// test every cel in the box's area for overlap
			for( int y=top; y <= bottom; y++ )
				for( int x=left; x<=right; x++ )
				{
					if( GetTile( x, y ) >= 0 )
					{
						intersect = true;
						Vector2 tileSize = new Vector2( w, h );
						Vector2 tilePos = Position + (new Vector2( x, y ) * tileSize);
						Hax.Intersect( boxPos, boxSize, tilePos, tileSize, ref testProj );
						if( testProj.Length() > projection.Length() )
							projection = testProj;
					}
				}

			projection = testProj;
			return intersect;
		}

		public override bool Intersect( Vector2 from, Vector2 to, ref Vector2 point, ref float slice )
		{
			return base.Intersect( from, to, ref point, ref slice );
		}

		public override bool Intersect( Vector2 point, ref Vector2 projection )
		{
			return base.Intersect( point, ref projection );
		}

		public override bool Intersect( Entity other, ref Vector2 projection )
		{
			return Intersect( other.Position, other.Size, ref projection );
		}

		#endregion

		#region XML

		// WHAT?!

        #endregion
    }
}
